/*Copyright (c) 2009 Michael Streif
 *
 *Permission is hereby granted, free of charge, to any person obtaining a copy
 *of this software and associated documentation files (the "Software"), to deal
 *in the Software without restriction, including without limitation the rights
 *to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *copies of the Software, and to permit persons to whom the Software is
 *furnished to do so, subject to the following conditions:
 *
 *The above copyright notice and this permission notice shall be included in
 *all copies or substantial portions of the Software.
 *
 *THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *THE SOFTWARE.
 */

#include "textmode.h"
#include "memcopy.h"
#include "kmalloc.h"
#include "string.h"
#include "asm.h"
#include "console.h"

#define VIDMEM_START	0xb8000									// start of video memory
#define VIDMEM_END		(VIDMEM_START + CONSOLE_SIZE)			// end of video memory

int8* getOutPtr(uint8 x, uint8 y);

int64 displayPage = 0;
int64 outputPage = 0;
int64 lastPage = 0;

uint8* textmodeBuffer;

void clearBuffer();

void nextEmptyPage()
{
	if (outputPage + 1 <= lastPage)
	{
		if (displayPage == outputPage)
		{
			memcopy((int8*) VIDMEM_START, &(textmodeBuffer[displayPage * CONSOLE_SIZE]), CONSOLE_SIZE);
			outputPage++;
			scrollDown();
		}
		else
		{
			outputPage++;
		}
	}
	else
	{
		clearBuffer();
		outputPage = 0;
		displayPage = 0;
		memcopy(&(textmodeBuffer[displayPage * CONSOLE_SIZE]), (uint8*) VIDMEM_START, CONSOLE_SIZE);
	}

	updateCursor(0, 0);
}

void clearBuffer()
{
	uint64 size = (lastPage + 1) * CONSOLE_SIZE;
	uint8* start = textmodeBuffer;

	while (size != 0)
	{
		*start++ = ' ';
		*start++ = BG_BLACK | FG_LIGHTGRAY;

		size -= 2;
	}
}

void scrollDown()
{
	// if we scroll away, down from the output page
	if (displayPage + 1 <= outputPage)
	{
		displayPage++;
		memcopy(&(textmodeBuffer[displayPage * CONSOLE_SIZE]), (uint8*) VIDMEM_START, CONSOLE_SIZE);
	}
}

void scrollUp()
{
	if (displayPage - 1 >= 0)
	{
		// if we scroll away, up from the output page
		if (displayPage == outputPage)
		{
			memcopy((int8*) VIDMEM_START, &(textmodeBuffer[displayPage * CONSOLE_SIZE]), CONSOLE_SIZE);
		}

		displayPage--;
		memcopy(&(textmodeBuffer[displayPage * CONSOLE_SIZE]), (uint8*) VIDMEM_START, CONSOLE_SIZE);

		hideCursor();
	}
}

bool setupTextmode(int16 pages)
{
	if (pages > 0)
	{
		textmodeBuffer = kmalloc(pages * CONSOLE_SIZE);
		lastPage = pages - 1;

		clearBuffer();

		updateCursor(0, 0);

		return 1;
	}

	return 0;
}

void setCell(char c, int8 color, uint8 x, uint8 y)
{
	if ((x < MAX_X) && (y < MAX_Y))
	{
		int8* video = getOutPtr(x, y);

		*video++ = c;
		*video = color;
	}
}

void setColor(int8 color, uint8 x, uint8 y)
{
	if ((x < MAX_X) && (y < MAX_Y))
	{
		int8* video = getOutPtr(x, y);

		*++video = color;
	}
}

inline int8* getOutPtr(uint8 x, uint8 y)
{
	if (outputPage == displayPage)
	{
		return (int8*) (int64) ((y * MAX_X + x) * 2 + VIDMEM_START);
	}
	else
	{
		return (int8*) (int64) ((y * MAX_X + x) * 2 + &(textmodeBuffer[outputPage * CONSOLE_SIZE]));
	}
}

void clearScreen(int8 color)
{
	int8* vidmem = getOutPtr(0, 0);

	for (int i = 0; i < CONSOLE_SIZE; i++)
	{
		*vidmem++ = ' ';
		*vidmem++ = color;
	}
}

char* getLine(uint8 line)
{
	if (line < MAX_Y)
	{
		char* retVal = kmalloc(LINE_SIZE);

		memcopy(getOutPtr(0, line), retVal, LINE_SIZE);

		return retVal;
	}

	return 0;
}

void writeLine(char* string, uint8 line, int8 color)
{
	if (line < MAX_Y)
	{
		uint8 x = strlen(string);
		int8* video = getOutPtr(0, line);

		while ((*string++ != 0) && (x-- != 0))
		{
			*video++ = *string;
			*video++ = color;
		}
	}
}

void clearLine(uint8 line, int8 color)
{
	if (line < MAX_Y)
	{
		int8* vidmem = getOutPtr(0, line);
		int i = 0;

		while (i < LINE_SIZE)
		{
			*vidmem++ = ' ';
			*vidmem++ = color;
			i += 2;
		}
	}
}

void getCell(char* c, int8* color, uint8 x, uint8 y)
{
	if ((x < MAX_X) && (y < MAX_Y))
	{
		int8* video = getOutPtr(x, y);

		*c = *video++;
		*color = *video;
	}

}

void getColor(int8* color, uint8 x, uint8 y)
{
	if ((x < MAX_X) && (y < MAX_Y))
	{
		int8* video = getOutPtr(x, y);

		*color = *++video;
	}

}

void updateCursor(uint8 x, uint8 y)
{
	uint16 pos = 0;

	if (displayPage == outputPage)
	{
		pos = (y * MAX_X) + x;
	}
	else
	{
		pos = 0xFFFF;
	}

	outb(0x3D4, 0x0F);
	outb(0x3D5, (uint8) pos);

	outb(0x3D4, 0x0E);
	outb(0x3D5, (uint8) (pos >> 8));
}

void hideCursor()
{
	outb(0x3D4, 0x0F);
	outb(0x3D5, 0xFF);

	outb(0x3D4, 0x0E);
	outb(0x3D5, 0xFF);
}
